
/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    wm8976.c
 * @brief   Asoc框架下的codec部分，wm8976编解码芯片的iis与控制接口相关操作
 *          参考sound\soc\codecs\uda134x.c
 *
 * @note    1、构建一个snd_soc_dai_driver
 *          2、构建一个snd_soc_codec_driver
 *          3、注册它们
 * @author  K
 * @version 0.0.1
 * @date    2021-08-07
 * @license MulanPSL-1.0
 *
 * 文件修改历史：
 * <时间>       | <版本>    | <作者>  | <描述>
 * 2021-08-07   | v0.0.1    | Kcode   | wm8976编解码芯片的iis与控制接口相关操作
 * -----------------------------------------------------------------------------
 ******************************************************************************/
 
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <asm/io.h>


#define WM8976_REGS_NUM 58  /**< WM8976寄存器的数量 */

#define WM8976_RATES SNDRV_PCM_RATE_8000_48000 /**< wm8976的采样率 */
#define WM8976_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)/**< wm8976的采样格式 */

/* WM8976与控制芯片的IO寄存器 */
static volatile unsigned int *gpbdat;
static volatile unsigned int *gpbcon;

/*!
 * @brief  获取wm8976的音量信息，如最大值、最小值
 * @return int
 */
int wm8976_info_vol(struct snd_kcontrol *kcontrol,
	struct snd_ctl_elem_info *uinfo)
{
	uinfo->type  = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 2;                   /**< 设置为双声道 */
	uinfo->value.integer.min = 0;       /**< 设置最小音量 */
	uinfo->value.integer.max = 63;      /**< 设置最大音量 */
    
	return 0;
}

/*!
 * @brief  获取wm8976当前的音量值
 * @return int
 */
int wm8976_get_vol(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);

    ucontrol->value.integer.value[0] = (snd_soc_read(codec, 52) & 0x3f);
    ucontrol->value.integer.value[1] = (snd_soc_read(codec, 53) & 0x3f);

    return 0;
}
    
/*!
 * @brief  设置wm8976当前的音量值
 * @return int
 */
int wm8976_put_vol(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
    unsigned int val;
    
    val = ucontrol->value.integer.value[0];    /**< 获取用户空间需求设置的值 */

    /* 设置的音量值写入寄存器 */
    snd_soc_write(codec, 52, (1 << 8) | val);

    val = ucontrol->value.integer.value[1];    /**< 获取用户空间需求设置的值 */

    /* 设置的音量值写入寄存器 */
    snd_soc_write(codec, 53, (1 << 8) | val);

    return 0;
}

/*!
 * 声卡音量控制相关的操作
 */
static const struct snd_kcontrol_new wm8976_vol_control = 
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, 
    .name = "Master Playback Volume", 
	.info = wm8976_info_vol, 
	.get  = wm8976_get_vol,
	.put  = wm8976_put_vol, 
};

/*!
 * @brief  uda1413与控制芯片的控制寄存器的初始化
 * @return vois
 */
static void wm8976_gpiocon_init(void)
{
    *gpbcon &= ~((3 << 4) | (3 << 6) | (3 << 8));
    *gpbcon |= ((1 << 4) | (1 << 6) | (1 << 8));
}

/*!
 * @brief  设置wm8976芯片的CSB引脚的电平
 * @param  [in] val  0-低电平，1-高电平
 * @return void
 */
static void set_csb(int val)
{
    if (val) {
        *gpbdat |= (1<<2);
    }
    else {
        *gpbdat &= ~(1<<2);
    }
}

/*!
 * @brief  设置wm8976芯片的CLK引脚的电平
 * @param  [in] val  0-低电平，1-高电平
 * @return void
 */
static void set_clk(int val)
{
    if (val) {
        *gpbdat |= (1<<4);
    }
    else {
        *gpbdat &= ~(1<<4);
    }
}

/*!
 * @brief  设置wm8976芯片的DAT引脚的电平
 * @param  [in] val  0-低电平，1-高电平
 * @return void
 */
static void set_dat(int val)
{
    if (val) {
        *gpbdat |= (1<<3);
    }
    else {
        *gpbdat &= ~(1<<3);
    }
}

/*!
 * @brief  往wm8976芯片的指定寄存器写入值
 * @return int 0：成功，-1：失败
 */
static int wm8976_write_reg(struct snd_soc_codec *codec, unsigned int reg,
    unsigned int data)
{
    u8 *cache = codec->reg_cache;
    int i;

    /* wm8976传输的是16位数据 = 高七位表示寄存器地 + 低9位表示寄存器的值 */
    unsigned short val = (reg << 9) | (data & 0x1ff);
    
    /*!
     * 先保存
     */
    if (reg >= WM8976_REGS_NUM)
        return -1;
    cache[reg] = data;

    /*!
     * 后写入硬件
     */
    set_csb(1);
    set_dat(1);
    set_clk(1);

    /*!
     * 把16位val值写入wm8976，从高位开始传输
     * 先让时钟变为低电平，然后时钟变为高电平，一个周期传输一位
     * 传输一位完毕后，移动到下一位
     */
    for (i = 0; i < 16; i++) {
        if (val & (1 << 15)){
            set_clk(0);
            set_dat(1);
            udelay(1);
            set_clk(1);
        } else {
            set_clk(0);
            set_dat(0);
            udelay(1);
            set_clk(1);
        }

        val = val << 1;
    }

    /*! 
     * 传输16位完成后，让csb信号产生低脉冲，写入wm8976
     */
    set_csb(0);
    udelay(1);

    /*!
     * 引脚恢复到高电平状态
     */
    set_csb(1);
    set_dat(1);
    set_clk(1); 
    
    return 0;
}

/*!
 * @brief  通过控制接口(GPB2,3,4)读写寄存器进行初始化
 * @return void
 */
 void wm8976_init_regs(struct snd_soc_codec *codec)
 {
    wm8976_gpiocon_init();
    
    /*!
     * 先软复位
     */
	wm8976_write_reg(codec, 0, 0);

    /*!
	 * OUT2的左/右声道打开
	 * 左/右通道输出混音打开
	 * 左/右DAC打开
	 */
	wm8976_write_reg(codec, 0x3, 0x6f);
	
	wm8976_write_reg(codec, 0x1, 0x1f);    //biasen,BUFIOEN.VMIDSEL=11b  
	wm8976_write_reg(codec, 0x2, 0x185);   //ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable

	wm8976_write_reg(codec, 0x6, 0x0);     //SYSCLK=MCLK  
	wm8976_write_reg(codec, 0x4, 0x10);    //16bit 		
	wm8976_write_reg(codec, 0x2B,0x10);    //BTL OUTPUT  
	wm8976_write_reg(codec, 0x9, 0x50);    //Jack detect enable  
	wm8976_write_reg(codec, 0xD, 0x21);    //Jack detect  
	wm8976_write_reg(codec, 0x7, 0x01);    //Jack detect 
 }

 static int wm8976_soc_probe(struct snd_soc_codec *codec)
 { 
    int ret = 0;
    
    wm8976_init_regs(codec);

    ret = snd_soc_add_codec_controls(codec, &wm8976_vol_control, 1); 
    return ret;
 }

/*!
 * 定义wm8976芯片的所有寄存器的默认值
 */
static const unsigned short s_wm8976_reg[WM8976_REGS_NUM] = {
};

/*!
 * @brief  读取wm8976芯片的指定寄存器的值
 * @return int 失败：-1，成功：寄存器的地址
 */
static inline unsigned int wm8976_read_reg_cache(struct snd_soc_codec *codec,
	unsigned int reg)
{
	u8 *cache = codec->reg_cache;

	if (reg >= WM8976_REGS_NUM)
		return -1;
	return cache[reg];
}

/*!
 * 核心结构体snd_soc_codec_driver(编解码芯片的控制相关)
 */
static struct snd_soc_codec_driver soc_codec_dev_wm8976 = {
    .probe = wm8976_soc_probe,

    /*!
     * wm8976的寄存器不支持当前的读操作
     * 因此：要知道某个寄存器的当前值，只能在写入时保存值
     * 所以：需要开辟一个内存大小为其寄存器大小的buffer
     */
    .reg_cache_size    = sizeof(s_wm8976_reg),
    .reg_word_size     = sizeof(u16),
    .reg_cache_default = s_wm8976_reg,
    .reg_cache_step    = 2,
    .read              = wm8976_read_reg_cache,
    .write             = wm8976_write_reg,
};

/*!
 * @brief  wm8976芯片的寄存器设置
 * @return int
 */
static int wm8976_hw_params(struct snd_pcm_substream *substream,
                    struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
    /*！
     * 根据params的值，设置wm8976的寄存器
     * 如：时钟的设置、格式
     */

	return 0;
}

/*!
 * 编解码芯片的具体操作函数
 */
static const struct snd_soc_dai_ops wm8976_dai_ops = {
	.hw_params	= wm8976_hw_params,

};

/*!
 * 核心结构体snd_soc_dai_driver(编解码芯片的相关属性)
 */
static struct snd_soc_dai_driver wm8976_dai = {
	.name = "wm8976-iis",
	/* 播放功能 */
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = WM8976_RATES,
		.formats = WM8976_FORMATS,
	},
	/* 录音功能 */
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = WM8976_RATES,
		.formats = WM8976_FORMATS,
	},
	/* pcm operations */
	.ops = &wm8976_dai_ops,
};

/*!
 * @brief 关闭设备的函数，必须得有，否则程序编译报错
 * @return void
 */
static void wm8976_dev_release(struct device *dev)
{}

/*!
 * 分配注册一个名为wm8976_dev的平台设备
 */
static struct platform_device wm8976_dev = {
    .name = "wm8976-codec",
    .id   = -1,
    .dev  = {
        .release = wm8976_dev_release,
    },
};

/*!
 * @brief  根据名字，连接到对应的平台驱动与设备时所调用的函数
 * @return int
 */
static int wm8976_probe(struct platform_device * pdev)
{    
    return snd_soc_register_codec(&pdev->dev, 
        &soc_codec_dev_wm8976, &wm8976_dai, 1);
}

/*!
 * @brief 关闭平台驱动的函数，必须得有，否则程序编译报错
 * @return int
 */
static int wm8976_remove(struct platform_device * pdev)
{
    snd_soc_unregister_codec(&pdev->dev);
    return 0;
}

/*!
 * 分配注册一个名为wm8976_dev的平台驱动
 */
struct platform_driver wm8976_drv = {
    .probe   = wm8976_probe,
    .remove  = wm8976_remove,
    .driver  = {
        .name = "wm8976-codec",
    },
};

/*!
 * @brief  uda1413与控制芯片的IO寄存器的映射
 * @return vois
 */
static void wm8976_gpio_map(void)
{
    gpbcon = ioremap(0x56000010, 4);
    gpbdat = ioremap(0x56000014, 4);
}

/*!
 * @brief  uda1413与控制芯片的IO寄存器的取消映射
 * @return vois
 */
static void wm8976_gpio_unmap(void)
{
    iounmap(gpbdat);
    iounmap(gpbcon);
}

/*!
 * @brief 入口函数
 * @return int 0：注册成功，1：注册失败
 */
static int wm8976_init(void)
{
    int ret = 0;

    wm8976_gpio_map();
    
    ret = platform_device_register(&wm8976_dev);
    if (ret) {
        printk("Can not register platform wm8976_dev!\n");
        goto fail;
    }
    
    ret = platform_driver_register(&wm8976_drv);
    if (ret) {
        printk("Can not register platform wm8976_drv!\n");
        platform_device_unregister(&wm8976_dev);
        goto fail;
    }

fail:    
    return ret;
}

/*!
 * @brief 出口函数
 * @return int，
 */
static void wm8976_exit(void)
{
    wm8976_gpio_unmap();

    platform_driver_unregister(&wm8976_drv);
    platform_device_unregister(&wm8976_dev);
}

/* 修饰 */
module_init(wm8976_init);
module_exit(wm8976_exit);

/* 协议 */
MODULE_LICENSE("GPL");


